package client

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"net"
	"strings"

	"gitee.com/xuender/oils/ids"
	"gitee.com/xuender/oils/level"
	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/pp"
	"gitee.com/xuender/pp/cui"
	"gitee.com/xuender/pp/nets"
	"gitee.com/xuender/pp/pb"
	"gitee.com/xuender/pp/robot"
	"gitee.com/xuender/pp/syncs"
	"google.golang.org/grpc"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/reflection"
	"google.golang.org/protobuf/proto"
)

// Service 客户端服务.
type Service struct {
	config *pp.Config
	us     *nets.UDPService
	rs     *robot.Service
	ls     *level.Service

	listener net.Listener

	me        *pb.User
	robot     ids.ID
	displayID ids.ID
	users     map[ids.ID]*serviceData
	ui        ChatUI
}

// NewService 新建服务.
func NewService(
	config *pp.Config,
	us *nets.UDPService,
	cs *cui.Service,
	rs *robot.Service,
	ls *level.Service,
) *Service {
	robot := ids.New()
	me := &pb.User{
		ID:    ids.Machine().Bytes(),
		Nick:  config.Nick,
		Port:  int32(config.RPCPort),
		State: pb.UserState_Online,
	}

	if config.UserID != "" {
		if id, err := ids.Short(config.UserID); err == nil {
			me.ID = id.Bytes()
		}
	}

	s := &Service{
		config:    config,
		me:        me,
		us:        us,
		rs:        rs,
		ls:        ls,
		robot:     robot,
		displayID: robot,
		users:     make(map[ids.ID]*serviceData),
	}
	// 判断是否是字符界面
	s.ui = cs
	s.ui.SetMe(me)
	s.ui.Sender(s.send)
	s.ui.Closer(s.Close)
	logs.SetLogger(s)
	_ = s.AddUser(&pb.User{
		Nick: "机器人",
		ID:   robot.Bytes(),
	})

	rs.Add(s.changeNick, "修改本人昵称", "昵称", "nick", "n")
	rs.Add(s.ui.Exit, "退出纸飞机", "退出", "关闭", "exit", "quit", "e", "q")

	s.Info(fmt.Sprintf("欢迎光临 [ %s ] .", s.me.Nick))
	s.Info(rs.Help(""))

	if s.me.Nick == "匿名" {
		s.Info("您没有设置昵称，请输入命令: n 新昵称")
	}

	return s
}

func (s *Service) changeNick(nick string) string {
	s.me.Nick = robot.GetArg(nick)
	for id, c := range s.users {
		if u, err := c.client.Hi(context.Background(), s.me); err == nil {
			s.users[id].user = u
		} else {
			s.RemoveUser(id)
		}
	}

	s.config.Nick = s.me.Nick
	_ = s.config.Save()

	return "昵称修改成: " + s.me.Nick
}

func (s *Service) send(msg *pb.Msg) {
	s.ui.Append(msg)

	if bytes.Equal(msg.To, s.robot.Bytes()) {
		s.ui.Append(&pb.Msg{
			From:     s.robot.Bytes(),
			To:       s.me.ID,
			FromNick: "机器人",
			Type:     pb.MsgType_Text,
			Data:     []byte(s.rs.Call(string(msg.Data))),
		})

		return
	}

	if bytes.Equal(msg.To, s.me.ID) {
		return
	}

	s.call(msg)
}

func (s *Service) log(t pb.MsgType, is ...interface{}) {
	if len(is) == 0 {
		return
	}

	ss := make([]string, len(is))

	for i, inter := range is {
		ss[i] = fmt.Sprintf("%v", inter)
	}

	s.ui.Append(&pb.Msg{
		From:     s.robot.Bytes(),
		To:       s.me.ID,
		FromNick: "机器人",
		Type:     t,
		Data:     []byte(strings.Join(ss, " ")),
	})
}

// SetFlags 标记.
func (s *Service) SetFlags(int) {}

// Info 提示信息.
func (s *Service) Info(is ...interface{}) {
	s.log(pb.MsgType_Info, is...)
}

// Error 错误信息.
func (s *Service) Error(is ...interface{}) {
	s.log(pb.MsgType_Info, is...)
}

// Warn 警告信息.
func (s *Service) Warn(is ...interface{}) {
	s.log(pb.MsgType_Info, is...)
}

// Debug 调试信息.
func (s *Service) Debug(is ...interface{}) {
	s.log(pb.MsgType_Debug, is...)
}

// Close 关闭服务.
func (s *Service) Close() {
	s.me.State = pb.UserState_Offline

	for _, u := range s.users {
		_, _ = u.client.Hi(context.Background(), s.me)
		_ = u.conn.Close()
	}

	if s.listener != nil {
		_ = s.listener.Close()
	}
}

// Hi 用户注册.
func (s *Service) Hi(ctx context.Context, user *pb.User) (*pb.User, error) {
	if user.State == pb.UserState_Offline {
		logs.Debug("删除用户", user.Nick)
		s.RemoveUser(ids.Bytes(user.ID))

		return s.me, nil
	}

	p, _ := peer.FromContext(ctx)
	addr := strings.Split(p.Addr.String(), ":")

	user.IP = addr[0]

	if err := s.AddUser(user); err != nil {
		return s.me, err
	}

	logs.Debug("修改用户信息", user.Nick, addr)

	return s.me, nil
}

// Say 对话.
func (s *Service) Say(ctx context.Context, msg *pb.Msg) (*pb.Msg, error) {
	// 核心业务
	switch msg.Type {
	case pb.MsgType_Text, pb.MsgType_Picture:
		if u, has := s.users[ids.Bytes(msg.From)]; has {
			msg.FromNick = u.user.Nick
		}

		msg.Key = msg.From

		s.putMsg(msg)
		s.ui.Append(msg)
	default:
		log.Println("消息类型未知", msg)
	}

	return msg, nil
}

func (s *Service) putMsg(msg *pb.Msg) error {
	key, err := s.ls.NextSequenceDESC(msg.Key)
	if err != nil {
		return err
	}

	return s.ls.Put(key, msg)
}

func (s *Service) call(msg *pb.Msg) {
	id := ids.Bytes(msg.To)

	if c, has := s.users[id]; has {
		if _, err := c.client.Say(context.Background(), msg); err == nil {
			logs.Debug("发送成功")
		} else {
			// 发送失败
			s.RemoveUser(id)
		}
	}

	msg.Key = msg.To

	s.putMsg(msg)
}

// AddUser 追加客户端.
func (s *Service) AddUser(user *pb.User) error {
	id := ids.Bytes(user.ID)
	if id == s.robot {
		s.ui.AddUser(user)

		return nil
	}

	ud, has := s.users[id]

	if has {
		old := ud.user.Nick
		s.users[id].user = user

		s.ui.ChangeNick(user)
		logs.Info(fmt.Sprintf("用户 [ %s ] 修改了昵称 [ %s ]", old, ud.user.Nick))
	} else {
		s.users[id] = &serviceData{user: user}
		s.ui.AddUser(user)
		logs.Info(fmt.Sprintf("用户 [ %s ] 上线了", user.Nick))

		conn, err := grpc.Dial(fmt.Sprintf("%s:%d", user.IP, user.Port), grpc.WithInsecure())
		if err != nil {
			// nolint: wrapcheck
			return err
		}

		s.users[id].conn = conn
		client := pb.NewChatClient(conn)
		s.users[id].client = client

		if _, err := client.Hi(context.Background(), s.me); err != nil {
			logs.Info(err.Error())
		}

		for _, m := range s.loadMsgs(id.Bytes()) {
			s.ui.Append(m)
		}
	}

	return nil
}

func (s *Service) loadMsgs(key []byte) []*pb.Msg {
	ret := []*pb.Msg{}

	s.ls.Query(key, 10, &ret, nil)

	return ret
}

// RemoveUser 删除客户.
func (s *Service) RemoveUser(id ids.ID) {
	logs.Debug("删除:", id)

	if c, has := s.users[id]; has {
		_ = c.conn.Close()
	}

	delete(s.users, id)
	s.ui.RemoveUser(id)
}

// Run 监听.
func (s *Service) Run() error {
	var err error

	s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.config.RPCPort))
	if err != nil {
		panic(err)
	}

	server := grpc.NewServer()

	pb.RegisterChatServer(server, s)
	reflection.Register(server)
	logs.Debug("启动监听")
	// 1秒钟后广播
	syncs.DelaySecond(1, s.brodacast)
	defer s.us.Close()
	// nolint: errcheck
	go s.us.Listen(s.udpCall)
	// nolint: errcheck
	go server.Serve(s.listener)

	if err := s.ui.Run(); err != nil {
		return ErrUI
	}

	return nil
}

func (s *Service) udpCall(addr *net.UDPAddr, bs []byte) {
	u := &pb.User{}

	if err := proto.Unmarshal(bs, u); err != nil {
		logs.Info(err.Error())

		return
	}
	// 判断是否是自己
	if bytes.Equal(u.ID, s.me.ID) {
		logs.Debug("我自己上线")

		return
	}

	u.IP = addr.IP.String()
	_ = s.AddUser(u)
	logs.Debug(u.Nick, u.State.String())
}

func (s *Service) brodacast() {
	data, err := proto.Marshal(s.me)
	if err != nil {
		panic(err)
	}

	_, _ = s.us.Call(s.config.UDPPort, data)
}
